﻿Imports System.IO
Imports System.Configuration
Imports System.Web
Imports System.Net.Mail
Imports System.Xml
Imports System.Threading
Imports log4net
Imports log4net.Config
Imports log4net.Core
Imports System.ServiceProcess
Imports System.Data
Imports System.Data.SqlClient
Imports Microsoft.Win32.TaskScheduler

<Assembly: log4net.Config.XmlConfigurator()> 
<Assembly: log4net.Config.Repository()> 

Public Class Service1
    ' timer
    Private R2Timer As System.Threading.Timer
    ' for multithreading
    Dim t As Thread

    Public Shared ReadOnly log As ILog = LogManager.GetLogger("XMLMonitorLogger")

    ' configuration values
    Dim svrName As String
    Dim fromEmail As String
    Dim toEmail As String
    Dim scanInt As String

    ''' <summary>
    ''' Service starup
    ''' </summary>
    ''' <param name="args"></param>
    ''' <remarks></remarks>
    Protected Overrides Sub OnStart(ByVal args() As String)
        ' initialize logging
        log4net.Config.XmlConfigurator.Configure()

        ' get configuration values
        LoadConfigValues()

        ' create instance of system timer
        Dim MonitorCallBack As New TimerCallback(AddressOf MonitorTimedEvent)

        ' scan interval in milliseconds
        Dim runEvery As Integer
        runEvery = (CInt(scanInt) * 60000)

        ' run timer
        R2Timer = New System.Threading.Timer(MonitorCallBack, Nothing, 1000, runEvery)

        ' log service start
        log.Info("XMLServerMonitor Started")
        SendEmail(toEmail, "XMLServerMonitor Started", "The XMLServerMonitor Service has been started. " & Now())

       
    End Sub

    ''' <summary>
    ''' log service stopping and send email alert of service stopping
    ''' </summary>
    ''' <remarks></remarks>
    Protected Overrides Sub OnStop()
        ' log service stop
        log.Info("XMLServerMonitor Stopped")
        SendEmail(toEmail, "XMLServerMonitor Stopped", "The XMLServerMonitor Service has been stopped. " & Now())
    End Sub

    ''' <summary>
    ''' Pull values from config.xml for email server, sender, recipient and scan interval
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub LoadConfigValues()
        Dim xmlDoc As New XmlDocument

        ' load config.xml
        xmlDoc.Load(System.Configuration.ConfigurationManager.AppSettings("configPath"))

        ' email server name
        svrName = xmlDoc.SelectSingleNode("/configuration/emailServer").InnerText

        ' email from
        fromEmail = xmlDoc.SelectSingleNode("/configuration/fromEmail").InnerText

        ' email to
        toEmail = xmlDoc.SelectSingleNode("/configuration/toEmail").InnerText

        ' service scan interval
        scanInt = xmlDoc.SelectSingleNode("/configuration/scanInterval").InnerText
    End Sub

    ''' <summary>
    ''' loop through xml file of all servers and check apps, services, sql jobs
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub RunMonitor()
        ' server name
        Dim sName As String

        ' server failure count
        Dim iCount As Integer = 0

        ' create XML document
        Dim xmlDoc As New XmlDocument

        ' servers node
        Dim serverNodes As XmlNodeList
        Dim serverNode As XmlNode

        ' server node
        Dim baseDataNodes As XmlNodeList

        ' servername node
        Dim innerNodes As XmlNodeList

        ' services,applications,jobs nodes
        Dim minorNodes As XmlNodeList

        ' path to XML file
        xmlDoc.Load(System.Configuration.ConfigurationManager.AppSettings("path"))

        ' main node
        serverNodes = xmlDoc.GetElementsByTagName("servers")

        ' loop through outer node 'servers'
        For Each serverNode In serverNodes
            baseDataNodes = serverNode.ChildNodes

            ' loop through server node
            For Each baseDataNode As XmlNode In baseDataNodes

                ' loop through servername node
                innerNodes = baseDataNode.ChildNodes
                For Each innerNode As XmlNode In innerNodes

                    ' loop through services, applications, jobs, tasks
                    minorNodes = innerNode.ChildNodes
                    For Each minorNode As XmlNode In minorNodes

                        ' get servername
                        If InStr(minorNode.Name, "#") > 0 Then
                            sName = minorNode.InnerText
                        Else
                            ' check if server is running
                            If IsProcessRunning(sName, "System") Then
                                ' type (service, application, job, task)
                                Dim typ As String
                                typ = minorNode.Name
                                ' status of type
                                Dim sts As String
                                If minorNode.Attributes("active").InnerText Then
                                    sts = "active"
                                Else
                                    sts = "disabled"
                                End If

                                ' if type is active run checks
                                If sts <> "disabled" Then
                                    Select Case typ
                                        Case "service"
                                            ' check if service is running
                                            If ServiceStatus(sName, minorNode.InnerText) <> "Running" Then
                                                SendEmail(toEmail, "Service Failure on: " & sName, "Service " & minorNode.InnerText & " is not running.")
                                                log.Error("SERVICE ERROR ON " & sName & " * : " & minorNode.InnerText & " has failed at " & Now())
                                            End If
                                        Case "application"
                                            ' check if application is running
                                            If Not IsProcessRunning(sName, minorNode.InnerText) Then
                                                SendEmail(toEmail, "Application Failure on: " & sName, "Application " & minorNode.InnerText & " is not running.")
                                                log.Error("APPLICATION ERROR ON " & sName & " * : " & minorNode.InnerText & " has failed at " & Now())
                                            End If
                                        Case "job"
                                            ' check last job status
                                            If JobOutcome(sName, minorNode.InnerText) = 0 Then
                                                SendEmail(toEmail, "SQL Job Failure on: " & sName, "SQL Job " & minorNode.InnerText _
                                                            & " failed on last run. Last run outcome was: " _
                                                            & LastOutcome(sName, minorNode.InnerText))
                                                log.Error("SLQ JOB ERROR ON " & sName & " * : " & minorNode.InnerText & " failed at " & Now() & " Last Outcome: " & LastOutcome(sName, minorNode.InnerText))
                                            End If
                                        Case "task"
                                            If Not TaskRunning(sName, minorNode.InnerText) Then
                                                SendEmail(toEmail, "Scheduled Task Disabled on: " & sName, "Scheduled Task: " & minorNode.InnerText)
                                                log.Error("TASK DISABLED ON " & sName & " * : " & minorNode.InnerText)
                                            End If
                                    End Select
                                End If
                            Else
                                ' Increment server failure count
                                iCount = iCount + 1
                            End If
                        End If
                    Next
                Next
            Next
        Next

        ' send server failure notification
        If iCount > 0 Then
            SendEmail(toEmail, "Server " & sName & " is not running.", "The Server " & sName & " was confirmed not running at " & Now())
            log.Error("SERVER " & sName & " FAILED * : failed at " & Now())
        End If
    End Sub

    ''' <summary>
    ''' Timer code
    ''' </summary>
    ''' <param name="state"></param>
    ''' <remarks></remarks>
    Private Sub MonitorTimedEvent(ByVal state As Object)
        'RunMonitor() ' this calls the main subroutine
        Try
            t = New Thread(AddressOf RunMonitor) ' create new thread
            t.Start() ' start the thread
        Catch ex As Exception
            log.Error("XMLServerMonitor Failure", ex)
        End Try
    End Sub

#Region "CommonSubsFunctions"
    ''' <summary>
    ''' Check the status of processes on a server
    ''' </summary>
    ''' <param name="strServer"></param>
    ''' <param name="strProcess"></param>
    ''' <returns>Boolean</returns>
    ''' <remarks></remarks>
    Public Shared Function IsProcessRunning(ByVal strServer, ByVal strProcess) As Boolean
        Dim Process, strObject
        Dim retVal As Boolean
        retVal = False

        strObject = "winmgmts:{impersonationLevel=impersonate}!//" & strServer

        Try
            For Each Process In GetObject(strObject).InstancesOf("win32_process")
                If UCase(Process.name) = UCase(strProcess) Then
                    retVal = True
                End If
            Next
        Catch ex As Exception
            log.Error("ERORR: * " & ex.ToString)
            retVal = False
        End Try

        Return retVal

    End Function

    ''' <summary>
    ''' Get the status of a service on a server
    ''' </summary>
    ''' <param name="strServer"></param>
    ''' <param name="strService"></param>
    ''' <returns>string with status</returns>
    ''' <remarks></remarks>
    Public Shared Function ServiceStatus(ByVal strServer, ByVal strService) As String
        Dim Serv As New ServiceController
        Dim ServStatus As String
        Dim retVal As String

        Serv.MachineName = strServer
        Serv.ServiceName = strService

        Try
            Serv.Refresh()
            ServStatus = Serv.Status.ToString
        Catch ex As Exception
            log.Error("ERORR: * " & ex.ToString)
            ServStatus = "Failure"
        End Try

        retVal = ServStatus

        Return retVal
    End Function

    ''' <summary>
    ''' Get the last outcome status of a job
    ''' </summary>
    ''' <param name="svr"></param>
    ''' <param name="job"></param>
    ''' <returns>1 indicates success 0 indicates failure</returns>
    ''' <remarks></remarks>
    Public Shared Function JobOutcome(ByVal svr As String, ByVal job As String) As Integer
        Dim retVal As Integer
        Dim cnStr As String = "Data Source=" & svr & ";Initial Catalog=msdb;Persist Security Info=True;User ID=" & System.Configuration.ConfigurationManager.AppSettings("user") & ";Password=" & System.Configuration.ConfigurationManager.AppSettings("password")
        Dim con As New SqlConnection(cnStr)
        Dim cmd As New SqlCommand("exec sp_help_job NULL,'" & job & "','TARGETS'")
        cmd.CommandType = CommandType.Text
        cmd.Connection = con

        Try
            con.Open()
            Dim r As SqlDataReader = cmd.ExecuteReader
            If r.HasRows Then
                r.Read()
                retVal = r("last_run_outcome")
                r.Close()
            End If
        Catch ex As Exception
            log.Error("ERORR: * " & ex.ToString)
        Finally
            con.Close()
            cmd.Dispose()
        End Try

        Return retVal
    End Function

    ''' <summary>
    ''' Get the last outcome message
    ''' </summary>
    ''' <param name="svr"></param>
    ''' <param name="job"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function LastOutcome(ByVal svr As String, ByVal job As String) As String
        Dim retVal As String = String.Empty
        Dim cnStr As String = "Data Source=" & svr & ";Initial Catalog=msdb;Persist Security Info=True;User ID=" & System.Configuration.ConfigurationManager.AppSettings("user") & ";Password=" & System.Configuration.ConfigurationManager.AppSettings("password")
        Dim con As New SqlConnection(cnStr)
        Dim cmd As New SqlCommand("exec sp_help_job NULL,'" & job & "','TARGETS'")
        cmd.CommandType = CommandType.Text
        cmd.Connection = con

        Try
            con.Open()
            Dim r As SqlDataReader = cmd.ExecuteReader
            If r.HasRows Then
                r.Read()
                retVal = r("last_outcome_message")
                r.Close()
            End If
        Catch ex As Exception
            log.Error("ERORR: * " & ex.ToString)
        Finally
            con.Close()
            cmd.Dispose()
        End Try

        Return retVal
    End Function

    ''' <summary>
    ''' Get state of scheduled task
    ''' </summary>
    ''' <param name="taskname"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function TaskRunning(ByVal servername As String, ByVal taskname As String) As Boolean
        Dim sUser As String = System.Configuration.ConfigurationManager.AppSettings("DomainUser")
        Dim sDomain As String = System.Configuration.ConfigurationManager.AppSettings("Domain")
        Dim sPass As String = System.Configuration.ConfigurationManager.AppSettings("DomainPass")
        Dim ts As New TaskService(servername, sUser, sDomain, sPass, False)
        Dim tsk As Task = ts.GetTask(taskname)
        If tsk.Enabled Then
            Return True
        Else
            Return False
        End If
    End Function

    ''' <summary>
    ''' Generic Email Handler
    ''' </summary>
    ''' <param name="eTo"></param>
    ''' <param name="sbj"></param>
    ''' <param name="msg"></param>
    ''' <remarks>Sends email through BRAVO</remarks>
    Private Sub SendEmail(ByVal eTo As String, ByVal sbj As String, ByVal msg As String)
        Dim mail As New System.Net.Mail.MailMessage()

        ' set address
        mail.From = New System.Net.Mail.MailAddress(fromEmail)
        mail.To.Add(eTo)

        ' subject
        mail.Subject = sbj

        ' set body
        mail.Body = msg

        ' set body to be html
        mail.IsBodyHtml = True

        ' send message
        Dim smtp As New System.Net.Mail.SmtpClient(svrName)
        ' use below for testing locally
        'Dim smtp As New SmtpClient()
        'smtp.DeliveryMethod = SmtpDeliveryMethod.SpecifiedPickupDirectory
        'smtp.PickupDirectoryLocation = "C:\development\roomrezadmin\Logs\Email"

        Try
            smtp.Send(mail)
        Catch ex As Exception
            log.Error("ERORR: * " & ex.ToString)
        End Try


        ' kill mail object
        mail.Dispose()
    End Sub
#End Region
End Class
